package compile;

import common.FileUtil;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

//每次的编译+运行 称为一个Task
public class Task {

    //通过一组常量，来约定临时文件的名字
    //临时文件的目录
    private  String WORK_DIR = null;
    //约定类名
    private  String CLASS = null;
    //约定文件名
    private  String CODE = null;
    //存放编译错误信息的文件名
    private  String COMPILE_ERROR = null;
    //约定存放运行时的标准输出的文件名
    private  String STDOUT =null;
    //约定存放运行时的标准错误的文件名
    private  String STDERR = null;

    public Task(){
        //在java中使用uuid，进行目录的唯一性
        WORK_DIR = System.getProperty("user.dir")+"./tmp/"+ UUID.randomUUID().toString()+"/";
        CLASS = "Solution";
        CODE = WORK_DIR+"Solution.java";
        COMPILE_ERROR = WORK_DIR+"compileError.txt";
        STDOUT = WORK_DIR+"stdout.txt";
        STDERR = WORK_DIR+"stderr.txt";
    }


    //这个Task类提供的核心方法就叫做compileAndRun
    //参数，要编译运行的java源代码
    //返回值，表示编译运行的结果  编译出错/运行出错/运行正确
    public Answer compileAndRun(Question question){
        Answer answer = new Answer();
        //0.准备好用来存放临时文件的目录
        File workDir = new File(WORK_DIR);
        if (!workDir.exists()){
            //可以创建多级目录
            workDir.mkdirs();
        }
        //进行安全性判定
        if (!checkCodeSafe(question.getCode())){
            System.out.println("用户提交了危险代码");
            answer.setError(3);
            answer.setReason("您提交的代码可能有危险，请换个思路");
            return answer;
        }

        //1.吧question 中的code写入到一个.java文件中
        FileUtil.writeFile(CODE,question.getCode());
        //2.创建子进程，调用javac进行编译，编译的时候需要有一个.java文件，当前是通过String的方式提供代码
            //如果编译出错，javac会把错误信息写入到stderr中，可以用一个专门的文件来保存  .compileError.txt
        //需要先把编译命令构造出来                                      -d 指定生成的.class文件在哪里
        String compileCmd = String.format("javac -encoding utf8 %s -d %s", CODE,WORK_DIR);
        System.out.println("编译命令"+compileCmd);
        //对于javac来说，不关注标准输出，只关注标准错误
        //一旦编译出错，通过标准错误返回
        CommandUtil.run(compileCmd,null, COMPILE_ERROR);
        //如果编译出错，错误信息就被记录到COMPILE_ERROR 文件中了
        String compileError = FileUtil.readFile(COMPILE_ERROR);
        if (!compileError.equals("")){
            //编译出错
            //直接返回answer，answer里面记录错误信息
            System.out.println("编译出错");
            answer.setError(1);//表示编译出错
            answer.setReason(compileError);
            return answer;
        }
        //3.创建子进程，调用java命令，并执行
            //运行程序也会把java子进程的标准输出和标准错误获取到  .stdout.txt    .stderr.txt
        String runCmd = String.format("java -classpath %s %s",WORK_DIR,CLASS);
        System.out.println("运行命令"+runCmd);
        CommandUtil.run(runCmd,STDOUT,STDERR);
        String runError = FileUtil.readFile(STDERR);
        if (!runError.equals("")){
            System.out.println("运行出错");
            answer.setError(2); //2.表示运行出错
            answer.setReason(runError);
            return answer;
        }

        //4.父进程获取到刚才的编译执行的结果，并打包成Answer对象
            //通过刚才约定的这几个文件约定即可
        System.out.println("运行正确");
        answer.setError(0);
        answer.setStdout(FileUtil.readFile(STDOUT));
        return answer;
    }

    private boolean checkCodeSafe(String code) {
        List<String> blockList = new ArrayList<>();
        //防止提交的代码运行恶意程序
        blockList.add("Runtime");
        blockList.add("exec");
        blockList.add("java.io");
        blockList.add("java.net");
        for(String target:blockList){
            int pos = code.indexOf(target);
            if (pos>=0){
                return false;
            }
        }
        return true;

    }


    public static void main(String[] args) {
        Task task = new Task();
        Question question = new Question();
        question.setCode("class Solution {\n" +
                "    public int[] twoSum(int[] nums, int target) {\n" +
                "        int[] a = {0,1};\n" +
                "        return a;\n" +
                "    }\n" +
                "\n" +
                "\n" +
                "\n" +
                "    public static void main(String[] args) {\n" +
                "\n" +
                "        //test1\n" +
                "        Solution solution = new Solution();\n" +
                "        int[] nums = {2,7,11,15};\n" +
                "        int target = 9;\n" +
                "        int[] result = solution.twoSum(nums,target);\n" +
                "        if (result.length == 2 && result[0] == 0 && result[1] == 1){\n" +
                "            System.out.println(\"testcase1 OK\");\n" +
                "        }else{\n" +
                "            System.out.println(\"testcase1 failed\");\n" +
                "        }\n" +
                "\n" +
                "        //test2\n" +
                "        int[] nums2 = {3,2,4};\n" +
                "        int target2 = 6;\n" +
                "        int[] result2 = solution.twoSum(nums2,target2);\n" +
                "        if (result2.length == 2 && result2[0] == 1 && result2[1] == 2){\n" +
                "            System.out.println(\"testcase2 OK\");\n" +
                "        }else{\n" +
                "            System.out.println(\"testcase2 failed\");\n" +
                "        }\n" +
                "\n" +
                "\n" +
                "\n" +
                "    }\n" +
                "\n" +
                "\n" +
                "}");
        Answer answer = task.compileAndRun(question);
        System.out.println(answer);

    }


}



















